/*
 * Copyright (c) 2009 Charles S. Wilson
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the 
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include <stdarg.h>
#include <ustr.h>
#include "confparse.h"
#include "confdata.h"
#include "tokenizer.h"
#include "util.h"

const char *
run2_parse_state_name (parse_state_t state)
{
  switch (state)
  {
    case RUN2_XMLPARSE_UNINITIALIZED:                     return "UNINITIALIZED"; break;
    case RUN2_XMLPARSE_UNKNOWN_STATE:                     return "UNKNOWN_STATE"; break;
    case RUN2_XMLPARSE_START:                             return "START"; break;
    case RUN2_XMLPARSE_INSIDE_RUN2_CONFIG:                return "INSIDE_RUN2_CONFIG"; break;
    case RUN2_XMLPARSE_INSIDE_SELF_OPTIONS:               return "INSIDE_SELF_OPTIONS"; break;
    case RUN2_XMLPARSE_INSIDE_SELF_OPTIONS_ARG:           return "INSIDE_SELF_OPTIONS_ARG"; break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL:                     return "INSIDE_GLOBAL"; break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT:         return "INSIDE_GLOBAL_ENVIRONMENT"; break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT_PREPEND: return "INSIDE_GLOBAL_ENVIRONMENT_PREPEND"; break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT_APPEND:  return "INSIDE_GLOBAL_ENVIRONMENT_APPEND"; break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT_SET:     return "INSIDE_GLOBAL_ENVIRONMENT_SET"; break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_AFTER_ENVIRONMENT:   return "INSIDE_GLOBAL_AFTER_ENVIRONMENT"; break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_TARGET:              return "INSIDE_GLOBAL_TARGET"; break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_TARGET_ARG:          return "INSIDE_GLOBAL_TARGET_ARG"; break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_AFTER_TARGET:        return "INSIDE_GLOBAL_AFTER_TARGET"; break;
    case RUN2_XMLPARSE_INSIDE_GDI:                        return "INSIDE_GDI"; break;
    case RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT:            return "INSIDE_GDI_ENVIRONMENT"; break;
    case RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT_PREPEND:    return "INSIDE_GDI_ENVIRONMENT_PREPEND"; break;
    case RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT_APPEND:     return "INSIDE_GDI_ENVIRONMENT_APPEND"; break;
    case RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT_SET:        return "INSIDE_GDI_ENVIRONMENT_SET"; break;
    case RUN2_XMLPARSE_INSIDE_GDI_AFTER_ENVIRONMENT:      return "INSIDE_GDI_AFTER_ENVIRONMENT"; break;
    case RUN2_XMLPARSE_INSIDE_GDI_TARGET:                 return "INSIDE_GDI_TARGET"; break;
    case RUN2_XMLPARSE_INSIDE_GDI_TARGET_ARG:             return "INSIDE_GDI_TARGET_ARG"; break;
    case RUN2_XMLPARSE_INSIDE_GDI_AFTER_TARGET:           return "INSIDE_GDI_AFTER_TARGET"; break;
    case RUN2_XMLPARSE_INSIDE_X11:                        return "INSIDE_X11"; break;
    case RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT:            return "INSIDE_X11_ENVIRONMENT"; break;
    case RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT_PREPEND:    return "INSIDE_X11_ENVIRONMENT_PREPEND"; break;
    case RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT_APPEND:     return "INSIDE_X11_ENVIRONMENT_APPEND"; break;
    case RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT_SET:        return "INSIDE_X11_ENVIRONMENT_SET"; break;
    case RUN2_XMLPARSE_INSIDE_X11_AFTER_ENVIRONMENT:      return "INSIDE_X11_AFTER_ENVIRONMENT"; break;
    case RUN2_XMLPARSE_INSIDE_X11_TARGET:                 return "INSIDE_X11_TARGET"; break;
    case RUN2_XMLPARSE_INSIDE_X11_TARGET_ARG:             return "INSIDE_X11_TARGET_ARG"; break;
    case RUN2_XMLPARSE_INSIDE_X11_AFTER_TARGET:           return "INSIDE_X11_AFTER_TARGET"; break;
    case RUN2_XMLPARSE_FINISH:                  return "FINISH"; break;
    default: /* not reached */
      return "internal error: undefined state"; break;
  }
}


char *
run2_parse_state_description (conf_parser_state_t* state)
{
  if (!state)
    return run2_strdup ("STATE=<NULL>");
  if (state->state == RUN2_XMLPARSE_UNKNOWN_STATE)
  {
    const char * statename = run2_parse_state_name (state->state);
    const char * prevname  = run2_parse_state_name (state->prev_state);
    const char * fmt = "STATE=%s [depth=%d, PREV=%s]";
    int len = strlen(statename) + strlen(prevname) + strlen(fmt) - 6 + 1;
    char * msg = (char *) run2_malloc (len + 12); /* 12 is max length of uint */
    snprintf (msg, len + 12, fmt, statename, state->unknown_depth, prevname);
    msg[len + 12 - 1] = '\0';
    return msg;
  }
  else
  {
    const char * statename = run2_parse_state_name (state->state);
    const char * fmt = "STATE=%s";
    int len = strlen(statename) + strlen(fmt) - 2 + 1;
    char * msg = (char *) run2_malloc (len);
    snprintf (msg, len, fmt, statename);
    msg[len - 1] = '\0';
    return msg;
  }
}

static void
run2_xml_vmessage (
  void *ctx,
  const char *progname, /* not checked! */
  const char *msgtype,  /* not checked! */
  const char *msg,
  va_list    args)
{
  conf_parser_state_t* pstate = (conf_parser_state_t*)ctx;
  if (!pstate)
  {
    if (!msg)
    {
      warnMsg("%s (xmlparse: STATE=<NULL>): unknown %s", progname, msgtype);
      return;
    }
    else
    {
      char * newfmt = NULL;
      char * newmsg = NULL;
      size_t len = 0;
      newfmt = run2_extend_str (msg,
                                  "%s (xmlparse: STATE=<NULL>): ", 0);
      len = strlen (newfmt) + strlen (progname) - 2 + 1;
      newmsg = (char *) run2_malloc (len);
      snprintf (newmsg, len, newfmt, progname);
      newmsg[len - 1] = '\0';
      vwarnMsg (newmsg, args);
      free (newmsg);
      free (newfmt);
    }
  }
  else
  {
    char * state_str = run2_parse_state_description (pstate);
    if (!msg)
    {
      warnMsg("%s (xmlparse: %s): unknown %s", progname, state_str, msgtype);
    }
    else
    {
      char * newfmt = NULL;
      char * newmsg = NULL;
      size_t len = 0;
      newfmt = run2_extend_str (msg,
                                  "%s (xmlparse: %s): ", 0);
      len = strlen (newfmt) + strlen (progname) + strlen (state_str) - 4 + 1;
      newmsg = (char *) run2_malloc (len);
      snprintf (newmsg, len, newfmt, progname, state_str);
      newmsg[len - 1] = '\0';
      vwarnMsg (newmsg, args);
      free (newmsg);
      free (newfmt);
    }
    free (state_str);
  }
}

void
run2_xml_warning(void *ctx, const char *msg, ...)
{
  va_list args;
  va_start (args, msg);
  run2_xml_vmessage (ctx, run2_get_program_name(), "warning", msg, args);
  va_end (args);
}

void
run2_xml_error(void *ctx, const char *msg, ...)
{
  va_list args;
  va_start (args, msg);
  run2_xml_vmessage (ctx, run2_get_program_name(), "error", msg, args);
  va_end (args);
}

void
run2_xml_fatal_error(void *ctx, const char *msg, ...)
{
  va_list args;
  va_start (args, msg);
  run2_xml_vmessage (ctx, run2_get_program_name(), "FATAL ERROR", msg, args);
  va_end (args);
  exit (1);
}

/* element and state stack management functions */

element_and_state_t *
run2_new_element_and_state (void          *element,
                            parse_state_t  state)
{
  /* allocate and assign values */
  element_and_state_t *esp = (element_and_state_t *) run2_malloc (
      sizeof (element_and_state_t));
  esp->element = element;
  esp->state = state;
  return esp;
}

void
run2_destroy_element_and_state (void *arg)
{
  /* only clean up structure; element is owned elsewhere */
  element_and_state_t *esp = (element_and_state_t*)arg;
  if (esp)
  {
    esp->element = NULL;
    free (esp);
  }
}

void
run2_xml_stackpush (conf_parser_state_t* state, void * newdata)
{
  cdsl_dlist_t *stack = state->stack;
  element_and_state_t *esp = run2_new_element_and_state (newdata, state->state);
  (void) cdsl_dlist_insert_after (stack, cdsl_dlist_end (stack), (void *)esp);
}

void *
run2_xml_stackpop (conf_parser_state_t* state)
{
  cdsl_dlist_t *stack = state->stack;
  cdsl_dlist_node_t * node = cdsl_dlist_remove_node (stack, cdsl_dlist_end (stack));
  void * v = cdsl_dlist_value (node);
  cdsl_dlist_destroy_node (node, NULL);
  return v;
}

void
run2_xml_stackdestroy (conf_parser_state_t* state)
{
  cdsl_dlist_destroy_list (state->stack, &run2_destroy_element_and_state);
}


string_buffer_t *
run2_xml_new_string_buffer (void)
{
  string_buffer_t *buffer = (string_buffer_t*) run2_malloc (sizeof (string_buffer_t));
  memset (buffer, 0, sizeof (string_buffer_t));
  buffer->buf = (xmlChar *) run2_malloc (1024 * sizeof (xmlChar));
  buffer->buf_sz = 1024;
  buffer->buf[0] = '\0';
  buffer->buf_used = 1;
  return buffer;
}

void
run2_xml_destroy_string_buffer (string_buffer_t *arg)
{
  if (arg)
  {
    free (arg->buf);
    arg->buf= NULL;
    arg->buf_sz = 0;
    arg->buf_used = 0;
  }
}

void
run2_xml_startdocument(void *ctx)
{
  conf_parser_state_t* pstate = (conf_parser_state_t*)ctx;
  debugMsg (3, "(%s) ctx=%p", __func__, ctx);
  if (pstate->data)
  {
    run2_confdata_delete(pstate->data);
    pstate->data = NULL;
  }
  pstate->data = NULL;
  pstate->stack = cdsl_dlist_new_list ();
  pstate->state = RUN2_XMLPARSE_START;
  pstate->prev_state = RUN2_XMLPARSE_UNINITIALIZED;
  pstate->unknown_depth = 0;
}

void
run2_xml_enddocument (void *ctx)
{
  conf_parser_state_t* pstate = (conf_parser_state_t*)ctx;
  debugMsg (3, "(%s) ctx=%p", __func__, ctx);

  if (pstate->unknown_depth != 0)
    run2_xml_warning (ctx, "unknown_depth != 0 (%d)", pstate->unknown_depth);
  if (cdsl_dlist_size (pstate->stack) != 0)
    run2_xml_warning (ctx, "element depth != 0 (%d)", cdsl_dlist_size (pstate->stack));

  /* clean up allocated members of ctx, *except* .data member */
  run2_xml_stackdestroy (pstate);
  pstate->stack = NULL;
  pstate->state = RUN2_XMLPARSE_FINISH;
}

void
run2_xml_characters (void           *ctx,
                     const xmlChar  *ch,
                     int             len)
{
  conf_parser_state_t* pstate = (conf_parser_state_t*)ctx;
  char * state_str = run2_parse_state_description (pstate);
  string_buffer_t *buffer;
  element_and_state_t *esp;

  debugMsg (3, "(%s) ctx=%p |%s|", __func__, ctx, state_str);
  switch (pstate->state)
  {
    case RUN2_XMLPARSE_INSIDE_SELF_OPTIONS_ARG:
    case RUN2_XMLPARSE_INSIDE_GLOBAL_TARGET_ARG:
    case RUN2_XMLPARSE_INSIDE_GDI_TARGET_ARG:
    case RUN2_XMLPARSE_INSIDE_X11_TARGET_ARG:
      /* top of stack should be our buffer */
      esp = (element_and_state_t *) cdsl_dlist_value (cdsl_dlist_rbegin (pstate->stack));
      if (!esp || esp->state != pstate->state || !esp->element)
      {
        errorMsg ("corrupted internal state: current state=%d, node=%p, node.state=%d, node.element=%p",
                  pstate->state, esp, (esp ? esp->state : RUN2_XMLPARSE_UNINITIALIZED),
                  (esp ? esp->element : 0));
        exit (1);
      }
      buffer = (string_buffer_t *)esp->element;

      /* do we need more space? */
      if (buffer->buf_used + len > buffer->buf_sz)
      {
        char * newptr = NULL;
        size_t newalloc = buffer->buf_sz * 2;
        newptr = (char *) run2_realloc (buffer->buf, newalloc);
        buffer->buf = newptr;
        buffer->buf_sz= newalloc;
      }

      /* copy characters */
      memcpy ((char *)(buffer->buf + buffer->buf_used - 1), (const char *)ch, len * sizeof (xmlChar));
      buffer->buf_used += len;
      buffer->buf[buffer->buf_used - 1] = '\0';
      break;
    default:
      break;
  }
}

static const char *RUN2_ELN_RUN2CONFIG   = "Run2Config";
static const char *RUN2_ELN_SELFOPTIONS  = "SelfOptions";
static const char *RUN2_ELN_GLOBAL       = "Global";
static const char *RUN2_ELN_GDI          = "GDI";
static const char *RUN2_ELN_X11          = "X11";
static const char *RUN2_ELN_ARG          = "Arg";
static const char *RUN2_ELN_ENVIRONMENT  = "Environment";
static const char *RUN2_ELN_TARGET       = "Target";
static const char *RUN2_ELN_PREPEND      = "Prepend";
static const char *RUN2_ELN_APPEND       = "Append";
static const char *RUN2_ELN_SET          = "Set";
static const char *RUN2_ATN_FILENAME     = "filename";
static const char *RUN2_ATN_STARTIN      = "startin";
static const char *RUN2_ATN_VAR          = "var";
static const char *RUN2_ATN_VALUE        = "value";

static void
handle_start_unexpected_element_inside (
    conf_parser_state_t *pstate,
    const xmlChar       *localname,
    const xmlChar       *prefix,
    const char          *inside)
{
  warnMsg ("Unexpected element <%s:%s> inside <%s>.",
      (prefix ? (const char*)prefix : "(null)"),
      (localname ? (const char*)localname : "(null)"),
      inside);
  pstate->prev_state = pstate->state;
  pstate->state = RUN2_XMLPARSE_UNKNOWN_STATE;
  pstate->unknown_depth++;
}


static void
handle_tgt_spec_attrs (
    conf_parser_state_t *pstate,
    run2_tgt_spec_t   *tgt,
    int                  nb_attributes,
    const xmlChar      **attributes)
{
  unsigned int index = 0;
  unsigned int indexAttribute;
  for (indexAttribute = 0; indexAttribute < nb_attributes;
       ++indexAttribute, index += 5 )
  {
    const xmlChar *localname = attributes[index];
    const xmlChar *prefix = attributes[index+1];
    const xmlChar *nsURI = attributes[index+2];
    const xmlChar *valueBegin = attributes[index+3];
    const xmlChar *valueEnd = attributes[index+4];
    if (!strcmp(localname, RUN2_ATN_FILENAME))
    {
      ptrdiff_t attrLen = valueEnd - valueBegin;
      Ustr * tmp = USTR_CHECK (ustr_dup_undef ((attrLen + 1) * sizeof (xmlChar)));
      char * ctmp = ustr_wstr (tmp);
      memcpy (ctmp, (const char *)valueBegin, attrLen * sizeof (xmlChar));
      ctmp[attrLen] = '\0';
      if (tgt->filename)
      {
        errorMsg ("filename attribute already exists: |%s| (new value |%s|)",
          tgt->filename, ustr_cstr (tmp));
        ustr_free (tmp);
        exit (1);
      }
      else
      {
        run2_tgt_spec_set_filename (tgt, tmp);
        ustr_free (tmp);
        debugMsg (3, "(%s) Found filename attribute (len=%d): |%s|", __func__, attrLen, tgt->filename);
      }
    }
    else if (!strcmp(localname, RUN2_ATN_STARTIN))
    {
      ptrdiff_t attrLen = valueEnd - valueBegin;
      Ustr * tmp = USTR_CHECK (ustr_dup_undef ((attrLen + 1) * sizeof (xmlChar)));
      char * ctmp = ustr_wstr (tmp);
      memcpy (ctmp, (const char *)valueBegin, attrLen * sizeof (xmlChar));
      ctmp[attrLen] = '\0';
      if (tgt->startin)
      {
        errorMsg ("startin attribute already exists: |%s| (new value |%s|)",
          tgt->startin, ustr_cstr (tmp));
        ustr_free (tmp);
        exit (1);
      }
      else
      {
        run2_tgt_spec_set_startin (tgt, tmp);
        ustr_free (tmp);
        debugMsg (3, "(%s) Found startin attribute (len=%d): |%s|", __func__, attrLen, tgt->startin);
      }
    }
  }
}

static void
handle_start_global_env(
    conf_parser_state_t *pstate,
    const xmlChar       *localname,
    const xmlChar       *prefix)
{
  element_and_state_t * esp = (element_and_state_t *)
      cdsl_dlist_value (cdsl_dlist_rbegin (pstate->stack));
  if (!esp || (esp->state != RUN2_XMLPARSE_INSIDE_GLOBAL) || !esp->element)
  {
    errorMsg ("corrupted internal state while parsing <%s:%s>: node=%p, node.state=%d, node.element=%p",
              (prefix ? (const char *)prefix : "(null)"),
              (localname ? (const char *)localname : "(null)"),
              esp, (esp ? esp->state : RUN2_XMLPARSE_UNINITIALIZED),
              (esp ? esp->element : 0));
    exit (1);
  }
  run2_global_t *global = (run2_global_t *)esp->element;
  pstate->state = RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT;
  run2_env_t *env = run2_global_get_env (global);
  run2_xml_stackpush (pstate, (void *)env);
}

static void
handle_start_global_tgt(
    conf_parser_state_t *pstate,
    const xmlChar       *localname,
    const xmlChar       *prefix,
    int                  nb_attributes,
    const xmlChar      **attributes)
{
  element_and_state_t * esp = (element_and_state_t *)
      cdsl_dlist_value (cdsl_dlist_rbegin (pstate->stack));
  if (!esp
      || ( (esp->state != RUN2_XMLPARSE_INSIDE_GLOBAL)
        && (esp->state != RUN2_XMLPARSE_INSIDE_GLOBAL_AFTER_ENVIRONMENT))
      || !esp->element)
  {
    errorMsg ("corrupted internal state while parsing <%s:%s>: node=%p, node.state=%d, node.element=%p",
              (prefix ? (const char *)prefix : "(null)"),
              (localname ? (const char *)localname : "(null)"),
              esp, (esp ? esp->state : RUN2_XMLPARSE_UNINITIALIZED),
              (esp ? esp->element : 0));
    exit (1);
  }
  run2_global_t *global = (run2_global_t *)esp->element;
  pstate->state = RUN2_XMLPARSE_INSIDE_GLOBAL_TARGET;
  run2_tgt_spec_t *tgt = run2_global_get_tgt (global);
  handle_tgt_spec_attrs (pstate, tgt, nb_attributes, attributes);
  run2_xml_stackpush (pstate, (void *)tgt);
}

static void
handle_start_gdi_env(
    conf_parser_state_t *pstate,
    const xmlChar       *localname,
    const xmlChar       *prefix)
{
  element_and_state_t * esp = (element_and_state_t *)
      cdsl_dlist_value (cdsl_dlist_rbegin (pstate->stack));
  if (!esp || (esp->state != RUN2_XMLPARSE_INSIDE_GDI) || !esp->element)
  {
    errorMsg ("corrupted internal state while parsing <%s:%s>: node=%p, node.state=%d, node.element=%p",
              (prefix ? (const char *)prefix : "(null)"),
              (localname ? (const char *)localname : "(null)"),
              esp, (esp ? esp->state : RUN2_XMLPARSE_UNINITIALIZED),
              (esp ? esp->element : 0));
    exit (1);
  }
  run2_tgtopts_t *gdi = (run2_tgtopts_t *)esp->element;
  pstate->state = RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT;
  run2_env_t *env = run2_tgtopts_get_env (gdi);
  run2_xml_stackpush (pstate, (void *)env);
}

static void
handle_start_gdi_tgt(
    conf_parser_state_t *pstate,
    const xmlChar       *localname,
    const xmlChar       *prefix,
    int                  nb_attributes,
    const xmlChar      **attributes)
{
  element_and_state_t * esp = (element_and_state_t *)
      cdsl_dlist_value (cdsl_dlist_rbegin (pstate->stack));
  if (!esp
      || ( (esp->state != RUN2_XMLPARSE_INSIDE_GDI)
        && (esp->state != RUN2_XMLPARSE_INSIDE_GDI_AFTER_ENVIRONMENT))
      || !esp->element)
  {
    errorMsg ("corrupted internal state while parsing <%s:%s>: node=%p, node.state=%d, node.element=%p",
              (prefix ? (const char *)prefix : "(null)"),
              (localname ? (const char *)localname : "(null)"),
              esp, (esp ? esp->state : RUN2_XMLPARSE_UNINITIALIZED),
              (esp ? esp->element : 0));
    exit (1);
  }
  run2_tgtopts_t *gdi = (run2_tgtopts_t *)esp->element;
  pstate->state = RUN2_XMLPARSE_INSIDE_GDI_TARGET;
  run2_tgt_spec_t *tgt = run2_tgtopts_get_tgt (gdi);
  handle_tgt_spec_attrs (pstate, tgt, nb_attributes, attributes);
  run2_xml_stackpush (pstate, (void *)tgt);
}

static void
handle_start_x11_env(
    conf_parser_state_t *pstate,
    const xmlChar       *localname,
    const xmlChar       *prefix)
{
  element_and_state_t * esp = (element_and_state_t *)
      cdsl_dlist_value (cdsl_dlist_rbegin (pstate->stack));
  if (!esp || (esp->state != RUN2_XMLPARSE_INSIDE_X11) || !esp->element)
  {
    errorMsg ("corrupted internal state while parsing <%s:%s>: node=%p, node.state=%d, node.element=%p",
              (prefix ? (const char *)prefix : "(null)"),
              (localname ? (const char *)localname : "(null)"),
              esp, (esp ? esp->state : RUN2_XMLPARSE_UNINITIALIZED),
              (esp ? esp->element : 0));
    exit (1);
  }
  run2_tgtopts_t *x11 = (run2_tgtopts_t *)esp->element;
  pstate->state = RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT;
  run2_env_t *env = run2_tgtopts_get_env (x11);
  run2_xml_stackpush (pstate, (void *)env);
}

static void
handle_start_x11_tgt(
    conf_parser_state_t *pstate,
    const xmlChar       *localname,
    const xmlChar       *prefix,
    int                  nb_attributes,
    const xmlChar      **attributes)
{
  element_and_state_t * esp = (element_and_state_t *)
      cdsl_dlist_value (cdsl_dlist_rbegin (pstate->stack));
  if (!esp
      || ( (esp->state != RUN2_XMLPARSE_INSIDE_X11)
        && (esp->state != RUN2_XMLPARSE_INSIDE_X11_AFTER_ENVIRONMENT))
      || !esp->element)
  {
    errorMsg ("corrupted internal state while parsing <%s:%s>: node=%p, node.state=%d, node.element=%p",
              (prefix ? (const char *)prefix : "(null)"),
              (localname ? (const char *)localname : "(null)"),
              esp, (esp ? esp->state : RUN2_XMLPARSE_UNINITIALIZED),
              (esp ? esp->element : 0));
    exit (1);
  }
  run2_tgtopts_t *x11 = (run2_tgtopts_t *)esp->element;
  pstate->state = RUN2_XMLPARSE_INSIDE_X11_TARGET;
  run2_tgt_spec_t *tgt = run2_tgtopts_get_tgt (x11);
  handle_tgt_spec_attrs (pstate, tgt, nb_attributes, attributes);
  run2_xml_stackpush (pstate, (void *)tgt);
}

static run2_env_spec_t *
handle_env_spec_attrs (
    conf_parser_state_t *pstate,
    run2_env_t        *env,
    const xmlChar       *localname,
    const xmlChar       *prefix,
    int                  nb_attributes,
    const xmlChar      **attributes)
{
  run2_env_spec_t *envspec = NULL;
  Ustr            *varname = NULL;
  Ustr            *varvalue= NULL;
  unsigned int index = 0;
  unsigned int indexAttribute;
  for (indexAttribute = 0; indexAttribute < nb_attributes;
       ++indexAttribute, index += 5 )
  {
    const xmlChar *localname = attributes[index];
    const xmlChar *prefix = attributes[index+1];
    const xmlChar *nsURI = attributes[index+2];
    const xmlChar *valueBegin = attributes[index+3];
    const xmlChar *valueEnd = attributes[index+4];
    if (!strcmp(localname, RUN2_ATN_VAR))
    {
      ptrdiff_t attrLen = valueEnd - valueBegin;
      Ustr * tmp = USTR_CHECK (ustr_dup_undef ((attrLen + 1) * sizeof (xmlChar)));
      char * ctmp = ustr_wstr (tmp);
      memcpy (ctmp, (const char *)valueBegin, attrLen * sizeof (xmlChar));
      ctmp[attrLen] = '\0';
      if (varname)
      {
        errorMsg ("var attribute already exists: |%s| (new value |%s|)",
          ustr_cstr (varname), ustr_cstr (tmp));
        ustr_free (tmp);
        exit (1);
      }
      else
      {
        varname = tmp;
        debugMsg (3, "(%s) Found var attribute (len=%d): |%s|", __func__, attrLen, ustr_cstr (varname));
      }
    }
    else if (!strcmp(localname, RUN2_ATN_VALUE))
    {
      ptrdiff_t attrLen = valueEnd - valueBegin;
      Ustr * tmp = USTR_CHECK (ustr_dup_undef ((attrLen + 1) * sizeof (xmlChar)));
      char * ctmp = ustr_wstr (tmp);
      memcpy (ctmp, (const char *)valueBegin, attrLen * sizeof (xmlChar));
      ctmp[attrLen] = '\0';
      if (varvalue)
      {
        errorMsg ("value already exists: |%s| (new value |%s|)",
          ustr_cstr (varvalue), ustr_cstr (tmp));
        ustr_free (tmp);
        exit (1);
      }
      else
      {
        varvalue = tmp;
        debugMsg (3, "(%s) Found value attribute (len=%d): |%s|", __func__, attrLen, ustr_cstr (varvalue));
      }
    }
  }
  if (varname && varvalue)
  {
    envspec = run2_env_get_var (env, varname);
    switch (pstate->state)
    {
    case RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT_PREPEND:
    case RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT_PREPEND:
    case RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT_PREPEND:
      run2_env_spec_prepend (envspec, varvalue);
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT_APPEND:
    case RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT_APPEND:
    case RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT_APPEND:
      run2_env_spec_append (envspec, varvalue);
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT_SET:
    case RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT_SET:
    case RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT_SET:
      run2_env_spec_set (envspec, varvalue);
      break;
    default:
      break;
    }
  }

  if (varname)
    ustr_free (varname);
  if (varvalue)
    ustr_free (varvalue);

  return envspec;
}

static void
handle_start_env_prepend (
    conf_parser_state_t *pstate,
    parse_state_t        next_state,
    const xmlChar       *localname,
    const xmlChar       *prefix,
    int                  nb_attributes,
    const xmlChar      **attributes)
{
  element_and_state_t * esp = (element_and_state_t *)
      cdsl_dlist_value (cdsl_dlist_rbegin (pstate->stack));
  run2_env_t      *env;
  run2_env_spec_t *envspec;

  if (!esp
      || ( (esp->state != RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT)
        && (esp->state != RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT)
        && (esp->state != RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT))
      || !esp->element)
  {
    errorMsg ("corrupted internal state while parsing <%s:%s>: node=%p, node.state=%d, node.element=%p",
              (prefix ? (const char *)prefix : "(null)"),
              (localname ? (const char *)localname : "(null)"),
              esp, (esp ? esp->state : RUN2_XMLPARSE_UNINITIALIZED),
              (esp ? esp->element : 0));
    exit (1);
  }
  env = (run2_env_t*)esp->element;
  pstate->state = next_state;
  envspec = handle_env_spec_attrs (pstate, env, localname, prefix, nb_attributes, attributes);
  run2_xml_stackpush (pstate, (void *)envspec);
}

static void
handle_start_env_append (
    conf_parser_state_t *pstate,
    parse_state_t        next_state,
    const xmlChar       *localname,
    const xmlChar       *prefix,
    int                  nb_attributes,
    const xmlChar      **attributes)
{
  run2_env_t      *env;
  run2_env_spec_t *envspec;

  element_and_state_t * esp = (element_and_state_t *)
      cdsl_dlist_value (cdsl_dlist_rbegin (pstate->stack));
  if (!esp
      || ( (esp->state != RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT)
        && (esp->state != RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT)
        && (esp->state != RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT))
      || !esp->element)
  {
    errorMsg ("corrupted internal state while parsing <%s:%s>: node=%p, node.state=%d, node.element=%p",
              (prefix ? (const char *)prefix : "(null)"),
              (localname ? (const char *)localname : "(null)"),
              esp, (esp ? esp->state : RUN2_XMLPARSE_UNINITIALIZED),
              (esp ? esp->element : 0));
    exit (1);
  }
  env = (run2_env_t*)esp->element;
  pstate->state = next_state;
  envspec = handle_env_spec_attrs (pstate, env, localname, prefix, nb_attributes, attributes);
  run2_xml_stackpush (pstate, (void *)envspec);
}

static void
handle_start_env_set (
    conf_parser_state_t *pstate,
    parse_state_t        next_state,
    const xmlChar       *localname,
    const xmlChar       *prefix,
    int                  nb_attributes,
    const xmlChar      **attributes)
{
  run2_env_t      *env;
  run2_env_spec_t *envspec;

  element_and_state_t * esp = (element_and_state_t *)
      cdsl_dlist_value (cdsl_dlist_rbegin (pstate->stack));
  if (!esp
      || ( (esp->state != RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT)
        && (esp->state != RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT)
        && (esp->state != RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT))
      || !esp->element)
  {
    errorMsg ("corrupted internal state while parsing <%s:%s>: node=%p, node.state=%d, node.element=%p",
              (prefix ? (const char *)prefix : "(null)"),
              (localname ? (const char *)localname : "(null)"),
              esp, (esp ? esp->state : RUN2_XMLPARSE_UNINITIALIZED),
              (esp ? esp->element : 0));
    exit (1);
  }
  env = (run2_env_t*)esp->element;
  pstate->state = next_state;
  envspec = handle_env_spec_attrs (pstate, env, localname, prefix, nb_attributes, attributes);
  run2_xml_stackpush (pstate, (void *)envspec);
}

void
run2_xml_startelementns(
    void           *ctx,
    const xmlChar  *localname,
    const xmlChar  *prefix,
    const xmlChar  *URI,
    int             nb_namespaces,
    const xmlChar **namespaces,
    int             nb_attributes,
    int             nb_defaulted,
    const xmlChar **attributes)
{
  conf_parser_state_t* pstate = (conf_parser_state_t*)ctx;
  char * state_str = run2_parse_state_description (pstate);
  debugMsg (3, "(%s) ctx=%p <%s:%s> in %s", __func__,
           ctx,
           (prefix ? (const char*)prefix : "(null)"),
           (localname ? (const char*)localname : "(null)"),
           state_str);
  free (state_str);

  switch (pstate->state)
  {
    case RUN2_XMLPARSE_UNINITIALIZED:
      errorMsg("Internal error: bad parser state");
      break;
    case RUN2_XMLPARSE_START:
      if (!strcmp (localname, RUN2_ELN_RUN2CONFIG))
      {
        pstate->state = RUN2_XMLPARSE_INSIDE_RUN2_CONFIG;
        pstate->data = run2_confdata_create ();
        run2_xml_stackpush (pstate, (void *)pstate->data);
      }
      else
      {
        warnMsg ("Expected <%s>. Got <%s:%s>.",
            RUN2_ELN_RUN2CONFIG,
            (prefix ? (const char*)prefix : "(null)"),
            (localname ? (const char*)localname : "(null)"));
        pstate->prev_state = pstate->state;
        pstate->state = RUN2_XMLPARSE_UNKNOWN_STATE;
        pstate->unknown_depth++;
      }
      break;
    case RUN2_XMLPARSE_INSIDE_RUN2_CONFIG:
      if (!strcmp (localname, RUN2_ELN_SELFOPTIONS))
      {
        pstate->state = RUN2_XMLPARSE_INSIDE_SELF_OPTIONS;
        run2_selfoptions_t * selfopt = run2_confdata_get_selfoptions (pstate->data);
        run2_xml_stackpush (pstate, (void *)selfopt);
      }
      else if (!strcmp (localname, RUN2_ELN_GLOBAL))
      {
        pstate->state = RUN2_XMLPARSE_INSIDE_GLOBAL;
        run2_global_t * global = run2_confdata_get_global (pstate->data);
        run2_xml_stackpush (pstate, (void *)global);
      }
      else if (!strcmp (localname, RUN2_ELN_GDI))
      {
        pstate->state = RUN2_XMLPARSE_INSIDE_GDI;
        run2_tgtopts_t * tgtopts = run2_confdata_get_gdi (pstate->data);
        run2_xml_stackpush (pstate, (void *)tgtopts);
      }
      else if (!strcmp (localname, RUN2_ELN_X11))
      {
        pstate->state = RUN2_XMLPARSE_INSIDE_X11;
        run2_tgtopts_t * tgtopts = run2_confdata_get_x11 (pstate->data);
        run2_xml_stackpush (pstate, (void *)tgtopts);
      }
      else
      {
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_RUN2CONFIG);
      }
      break;
    case RUN2_XMLPARSE_INSIDE_SELF_OPTIONS:
      if (!strcmp (localname, RUN2_ELN_ARG))
      {
        pstate->state = RUN2_XMLPARSE_INSIDE_SELF_OPTIONS_ARG;
        string_buffer_t *buffer = run2_xml_new_string_buffer ();
        run2_xml_stackpush (pstate, (void *)buffer);
      }
      else
      {
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_SELFOPTIONS);
      }
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL:
      if (!strcmp (localname, RUN2_ELN_ENVIRONMENT))
      {
        handle_start_global_env (pstate, prefix, localname);
      }
      else if (!strcmp (localname, RUN2_ELN_TARGET))
      {
        handle_start_global_tgt (pstate, localname, prefix, nb_attributes, attributes);
      }
      else
      {
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_GLOBAL);
      }
      break;
    case RUN2_XMLPARSE_INSIDE_GDI:
      if (!strcmp (localname, RUN2_ELN_ENVIRONMENT))
      {
        handle_start_gdi_env (pstate, prefix, localname);
      }
      else if (!strcmp (localname, RUN2_ELN_TARGET))
      {
        handle_start_gdi_tgt (pstate, localname, prefix, nb_attributes, attributes);
      }
      else
      {
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_GDI);
      }
      break;
    case RUN2_XMLPARSE_INSIDE_X11:
      if (!strcmp (localname, RUN2_ELN_ENVIRONMENT))
      {
        handle_start_x11_env (pstate, prefix, localname);
      }
      else if (!strcmp (localname, RUN2_ELN_TARGET))
      {
        handle_start_x11_tgt (pstate, localname, prefix, nb_attributes, attributes);
      }
      else
      {
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_X11);
      }
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT:
      if (!strcmp (localname, RUN2_ELN_PREPEND))
      {
        handle_start_env_prepend (pstate, RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT_PREPEND,
            localname, prefix, nb_attributes, attributes);
      }
      else if (!strcmp (localname, RUN2_ELN_APPEND))
      {
        handle_start_env_append (pstate, RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT_APPEND,
            localname, prefix, nb_attributes, attributes);
      }
      else if (!strcmp (localname, RUN2_ELN_SET))
      {
        handle_start_env_set (pstate, RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT_SET,
            localname, prefix, nb_attributes, attributes);
      }
      else
      {
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_ENVIRONMENT);
      }
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_AFTER_ENVIRONMENT:
      if (!strcmp (localname, RUN2_ELN_TARGET))
      {
        handle_start_global_tgt (pstate, localname, prefix, nb_attributes, attributes);
      }
      else
      {
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_GLOBAL);
      }
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_TARGET:
      if (!strcmp (localname, RUN2_ELN_ARG))
      {
        pstate->state = RUN2_XMLPARSE_INSIDE_GLOBAL_TARGET_ARG;
        string_buffer_t *buffer = run2_xml_new_string_buffer ();
        run2_xml_stackpush (pstate, (void *)buffer);
      }
      else
      {
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_TARGET);
      }
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_AFTER_TARGET:
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_GLOBAL);
      break;
    case RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT:
      if (!strcmp (localname, RUN2_ELN_PREPEND))
      {
        handle_start_env_prepend (pstate, RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT_PREPEND,
            localname, prefix, nb_attributes, attributes);
      }
      else if (!strcmp (localname, RUN2_ELN_APPEND))
      {
        handle_start_env_append (pstate, RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT_APPEND,
            localname, prefix, nb_attributes, attributes);
      }
      else if (!strcmp (localname, RUN2_ELN_SET))
      {
        handle_start_env_set (pstate, RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT_SET,
            localname, prefix, nb_attributes, attributes);
      }
      else
      {
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_ENVIRONMENT);
      }
      break;
    case RUN2_XMLPARSE_INSIDE_GDI_AFTER_ENVIRONMENT:
      if (!strcmp (localname, RUN2_ELN_TARGET))
      {
        handle_start_gdi_tgt (pstate, localname, prefix, nb_attributes, attributes);
      }
      else
      {
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_GDI);
      }
      break;
    case RUN2_XMLPARSE_INSIDE_GDI_TARGET:
      if (!strcmp (localname, RUN2_ELN_ARG))
      {
        pstate->state = RUN2_XMLPARSE_INSIDE_GDI_TARGET_ARG;
        string_buffer_t *buffer = run2_xml_new_string_buffer ();
        run2_xml_stackpush (pstate, (void *)buffer);
      }
      else
      {
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_TARGET);
      }
      break;
    case RUN2_XMLPARSE_INSIDE_GDI_AFTER_TARGET:
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_GDI);
      break;
    case RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT:
      if (!strcmp (localname, RUN2_ELN_PREPEND))
      {
        handle_start_env_prepend (pstate, RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT_PREPEND,
            localname, prefix, nb_attributes, attributes);
      }
      else if (!strcmp (localname, RUN2_ELN_APPEND))
      {
        handle_start_env_append (pstate, RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT_APPEND,
            localname, prefix, nb_attributes, attributes);
      }
      else if (!strcmp (localname, RUN2_ELN_SET))
      {
        handle_start_env_set (pstate, RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT_SET,
            localname, prefix, nb_attributes, attributes);
      }
      else
      {
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_ENVIRONMENT);
      }
      break;
    case RUN2_XMLPARSE_INSIDE_X11_AFTER_ENVIRONMENT:
      if (!strcmp (localname, RUN2_ELN_TARGET))
      {
        handle_start_x11_tgt (pstate, localname, prefix, nb_attributes, attributes);
      }
      else
      {
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_X11);
      }
      break;
    case RUN2_XMLPARSE_INSIDE_X11_TARGET:
      if (!strcmp (localname, RUN2_ELN_ARG))
      {
        pstate->state = RUN2_XMLPARSE_INSIDE_X11_TARGET_ARG;
        string_buffer_t *buffer = run2_xml_new_string_buffer ();
        run2_xml_stackpush (pstate, (void *)buffer);
      }
      else
      {
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_TARGET);
      }
      break;
    case RUN2_XMLPARSE_INSIDE_X11_AFTER_TARGET:
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_X11);
      break;
    case RUN2_XMLPARSE_INSIDE_SELF_OPTIONS_ARG:
    case RUN2_XMLPARSE_INSIDE_GLOBAL_TARGET_ARG:
    case RUN2_XMLPARSE_INSIDE_GDI_TARGET_ARG:
    case RUN2_XMLPARSE_INSIDE_X11_TARGET_ARG:
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_ARG);
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT_PREPEND:
    case RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT_PREPEND:
    case RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT_PREPEND:
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_PREPEND);
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT_APPEND:
    case RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT_APPEND:
    case RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT_APPEND:
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_APPEND);
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT_SET:
    case RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT_SET:
    case RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT_SET:
        handle_start_unexpected_element_inside (
          pstate, localname, prefix, RUN2_ELN_SET);
      break;

    case RUN2_XMLPARSE_FINISH:
      warnMsg ("There should be no elements here. Found <%s:%s>",
         (prefix ? (const char*)prefix : "(null)"),
         (localname ? (const char*)localname : "(null)"));
      pstate->prev_state = pstate->state;
      pstate->state = RUN2_XMLPARSE_UNKNOWN_STATE;
      pstate->unknown_depth++;
      break;
    case RUN2_XMLPARSE_UNKNOWN_STATE:
      pstate->unknown_depth++;
      break;
  }

}

static int
wrong_closure (
    conf_parser_state_t *pstate,
    const xmlChar       *localname,
    const xmlChar       *prefix,
    const char          *inside)
{
  if (strcmp (localname, inside) != 0)
  {
    warnMsg ("should find </%s> here. Found </%s:%s>",
        inside,
        (prefix ? (const char*)prefix : "(null)"),
        (localname ? (const char*)localname : "(null)"));
    return 1;
  }
  return 0;
}

static string_buffer_t *
handle_end_arg_core (
    conf_parser_state_t *pstate,
    parse_state_t       expected_node_state,
    parse_state_t       expected_parent_node_state,
    const xmlChar       *localname,
    const xmlChar       *prefix)
{
  element_and_state_t * esp;
  string_buffer_t * buffer;
  char *p;
  size_t i = 0;

  esp = run2_xml_stackpop (pstate);
  if (!esp || esp->state != expected_node_state || !esp->element)
  {
    errorMsg ("corrupted internal state while parsing </%s>: node=%p, node.state=%d, node.element=%p",
              RUN2_ELN_ARG, esp, (esp ? esp->state : RUN2_XMLPARSE_UNINITIALIZED),
              (esp ? esp->element : 0));
    exit (1);
  }
  buffer = (string_buffer_t *)esp->element;
  run2_destroy_element_and_state (esp);

  /* now, top of stack should be an object of the correct type */
  esp = (element_and_state_t *) cdsl_dlist_value (cdsl_dlist_rbegin (pstate->stack));
  if (!esp || esp->state != expected_parent_node_state || !esp->element)
  {
    errorMsg ("corrupted internal state while parsing </%s>: node=%p, node.state=%d, node.element=%p",
              RUN2_ELN_ARG, esp, (esp ? esp->state : RUN2_XMLPARSE_UNINITIALIZED),
              (esp ? esp->element : 0));
    exit (1);
  }

  /* replace '-!-' with '--'.  This allows, e.g.
          <Arg>--long-option</Arg>
     to be commented-out.  The following is illegal, because
     -- may not appear inside comments:
          <!-- <Arg>--long-option</Arg> -->
     However, this IS legal
          <!-- <Arg>-!-long-option</Arg> -->
     So, for convenience, we always map
          <Arg>-!-long-option</Arg>
     as if it actually contains --long-option.
   */
  p = buffer->buf;
  i = 0;
  for (i = 0; i < buffer->buf_used - 3; i++, p++)
    {
      if (strncmp (p, "-!-", 3) == 0)
        {
          memmove (p, p+1, buffer->buf_used - i - 1);
          *p = '-';
          buffer->buf_used--;
        }
    }
  return buffer;
}

static void
handle_end_selfoptions_arg (
    conf_parser_state_t *pstate,
    const xmlChar       *localname,
    const xmlChar       *prefix)
{
  element_and_state_t * esp;
  string_buffer_t * buffer;
  int argc = 0;
  const char **argv = NULL;
  int rc;

  buffer = handle_end_arg_core (pstate,
                                RUN2_XMLPARSE_INSIDE_SELF_OPTIONS_ARG,
                                RUN2_XMLPARSE_INSIDE_SELF_OPTIONS,
                                localname,
                                prefix);

  /* now, top of stack should be SELF_OPTIONS object (already validated above) */
  esp = (element_and_state_t *) cdsl_dlist_value (cdsl_dlist_rbegin (pstate->stack));

  /* tokenize */
  if ((rc = run2_split_string ((const char *)buffer->buf, &argc, &argv)) != 0)
    {
      errorMsg ("Could not tokenize (error code %d) |%s|", rc, buffer->buf);
      Ustr * tmp = USTR_CHECK (ustr_dup_buf ((char *)buffer->buf, buffer->buf_used));
      run2_selfoptions_add_arg ((run2_selfoptions_t*)esp->element, tmp);
      ustr_free (tmp);
    }
  else
    {
      int i;
      Ustr * tmp = USTR_CHECK (ustr_dup_empty ());
      for (i = 0; i < argc; i++)
        {
          ustr_set_empty (&tmp);
          ustr_add_buf (&tmp, argv[i], strlen (argv[i]));
          run2_selfoptions_add_arg ((run2_selfoptions_t*)esp->element, tmp);
        }
      ustr_free (tmp);
      run2_freeargv ((char **)argv);
      argv = NULL;
    }
  run2_xml_destroy_string_buffer (buffer);
  buffer = NULL;
  pstate->state = RUN2_XMLPARSE_INSIDE_SELF_OPTIONS;
}

static void
handle_end_global_target_arg (
    conf_parser_state_t *pstate,
    const xmlChar       *localname,
    const xmlChar       *prefix)
{
  element_and_state_t * esp;
  string_buffer_t * buffer;
  int argc = 0;
  const char **argv = NULL;
  int rc;

  buffer = handle_end_arg_core (pstate,
                                RUN2_XMLPARSE_INSIDE_GLOBAL_TARGET_ARG,
                                RUN2_XMLPARSE_INSIDE_GLOBAL_TARGET,
                                localname,
                                prefix);

  /* now, top of stack should be TGT_SPEC object (already validated above) */
  esp = (element_and_state_t *) cdsl_dlist_value (cdsl_dlist_rbegin (pstate->stack));

  /* tokenize */
  if ((rc = run2_split_string ((const char *)buffer->buf, &argc, &argv)) != 0)
    {
      errorMsg ("Could not tokenize (error code %d) |%s|", rc, buffer->buf);
      Ustr * tmp = USTR_CHECK (ustr_dup_buf ((char *)buffer->buf, buffer->buf_used));
      run2_tgt_spec_add_arg ((run2_tgt_spec_t*)esp->element, tmp);
      ustr_free (tmp);
    }
  else
    {
      int i;
      Ustr * tmp = USTR_CHECK (ustr_dup_empty ());
      for (i = 0; i < argc; i++)
        {
          ustr_set_empty (&tmp);
          ustr_add_buf (&tmp, argv[i], strlen (argv[i]));
          run2_tgt_spec_add_arg ((run2_tgt_spec_t*)esp->element, tmp);
        }
      ustr_free (tmp);
      run2_freeargv ((char **)argv);
      argv = NULL;
    }
  run2_xml_destroy_string_buffer (buffer);
  buffer = NULL;
  pstate->state = RUN2_XMLPARSE_INSIDE_GLOBAL_TARGET;
}

static void
handle_end_gdi_target_arg (
    conf_parser_state_t *pstate,
    const xmlChar       *localname,
    const xmlChar       *prefix)
{
  element_and_state_t * esp;
  string_buffer_t * buffer;
  int argc = 0;
  const char **argv = NULL;
  int rc;

  buffer = handle_end_arg_core (pstate,
                                RUN2_XMLPARSE_INSIDE_GDI_TARGET_ARG,
                                RUN2_XMLPARSE_INSIDE_GDI_TARGET,
                                localname,
                                prefix);

  /* now, top of stack should be TGT_SPEC object (already validated above) */
  esp = (element_and_state_t *) cdsl_dlist_value (cdsl_dlist_rbegin (pstate->stack));

  /* tokenize */
  if ((rc = run2_split_string ((const char *)buffer->buf, &argc, &argv)) != 0)
    {
      errorMsg ("Could not tokenize (error code %d) |%s|", rc, buffer->buf);
      Ustr * tmp = USTR_CHECK (ustr_dup_buf ((char *)buffer->buf, buffer->buf_used));
      run2_tgt_spec_add_arg ((run2_tgt_spec_t*)esp->element, tmp);
      ustr_free (tmp);
    }
  else
    {
      int i;
      Ustr * tmp = USTR_CHECK (ustr_dup_empty ());
      for (i = 0; i < argc; i++)
        {
          ustr_set_empty (&tmp);
          ustr_add_buf (&tmp, argv[i], strlen (argv[i]));
          run2_tgt_spec_add_arg ((run2_tgt_spec_t*)esp->element, tmp);
        }
      ustr_free (tmp);
      run2_freeargv ((char **)argv);
      argv = NULL;
    }
  run2_xml_destroy_string_buffer (buffer);
  buffer = NULL;
  pstate->state = RUN2_XMLPARSE_INSIDE_GDI_TARGET;
}

static void
handle_end_x11_target_arg (
    conf_parser_state_t *pstate,
    const xmlChar       *localname,
    const xmlChar       *prefix)
{
  element_and_state_t * esp;
  string_buffer_t * buffer;
  int argc = 0;
  const char **argv = NULL;
  int rc;

  buffer = handle_end_arg_core (pstate,
                                RUN2_XMLPARSE_INSIDE_X11_TARGET_ARG,
                                RUN2_XMLPARSE_INSIDE_X11_TARGET,
                                localname,
                                prefix);

  /* now, top of stack should be TGT_SPEC object (already validated above) */
  esp = (element_and_state_t *) cdsl_dlist_value (cdsl_dlist_rbegin (pstate->stack));

  /* tokenize */
  if ((rc = run2_split_string ((const char *)buffer->buf, &argc, &argv)) != 0)
    {
      errorMsg ("Could not tokenize (error code %d) |%s|", rc, buffer->buf);
      Ustr * tmp = USTR_CHECK (ustr_dup_buf ((char *)buffer->buf, buffer->buf_used));
      run2_tgt_spec_add_arg ((run2_tgt_spec_t*)esp->element, tmp);
      ustr_free (tmp);
    }
  else
    {
      int i;
      Ustr * tmp = USTR_CHECK (ustr_dup_empty ());
      for (i = 0; i < argc; i++)
        {
          ustr_set_empty (&tmp);
          ustr_add_buf (&tmp, argv[i], strlen (argv[i]));
          run2_tgt_spec_add_arg ((run2_tgt_spec_t*)esp->element, tmp);
        }
      ustr_free (tmp);
      run2_freeargv ((char **)argv);
      argv = NULL;
    }
  run2_xml_destroy_string_buffer (buffer);
  buffer = NULL;
  pstate->state = RUN2_XMLPARSE_INSIDE_X11_TARGET;
}

void
run2_xml_endelementns(
    void          *ctx,
    const xmlChar *localname,
    const xmlChar *prefix,
    const xmlChar *URI)
{
  conf_parser_state_t* pstate = (conf_parser_state_t*)ctx;
  char * state_str = run2_parse_state_description (pstate);
  element_and_state_t * esp;
  string_buffer_t * buffer;

  debugMsg (3, "(%s) ctx=%p <%s:%s> in %s", __func__,
           ctx,
           (prefix ? (const char*)prefix : "(null)"),
           (localname ? (const char*)localname : "(null)"),
           state_str);
  free (state_str);

  switch (pstate->state)
  {
    case RUN2_XMLPARSE_UNINITIALIZED:
      errorMsg("Internal error: bad parser state");
      break;
    case RUN2_XMLPARSE_START:
      warnMsg ("Should not be closing any elements in this state");
      break;
    case RUN2_XMLPARSE_INSIDE_RUN2_CONFIG:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_RUN2CONFIG);
      pstate->state = RUN2_XMLPARSE_FINISH;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_SELF_OPTIONS:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_SELFOPTIONS);
      pstate->state = RUN2_XMLPARSE_INSIDE_RUN2_CONFIG;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL:
    case RUN2_XMLPARSE_INSIDE_GLOBAL_AFTER_ENVIRONMENT:
    case RUN2_XMLPARSE_INSIDE_GLOBAL_AFTER_TARGET:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_GLOBAL);
      pstate->state = RUN2_XMLPARSE_INSIDE_RUN2_CONFIG;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_GDI:
    case RUN2_XMLPARSE_INSIDE_GDI_AFTER_ENVIRONMENT:
    case RUN2_XMLPARSE_INSIDE_GDI_AFTER_TARGET:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_GDI);
      pstate->state = RUN2_XMLPARSE_INSIDE_RUN2_CONFIG;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_X11:
    case RUN2_XMLPARSE_INSIDE_X11_AFTER_ENVIRONMENT:
    case RUN2_XMLPARSE_INSIDE_X11_AFTER_TARGET:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_X11);
      pstate->state = RUN2_XMLPARSE_INSIDE_RUN2_CONFIG;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_SELF_OPTIONS_ARG:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_ARG);
      handle_end_selfoptions_arg (pstate, localname, prefix);
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_TARGET_ARG:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_ARG);
      handle_end_global_target_arg (pstate, localname, prefix);
      break;
    case RUN2_XMLPARSE_INSIDE_GDI_TARGET_ARG:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_ARG);
      handle_end_gdi_target_arg (pstate, localname, prefix);
      break;
    case RUN2_XMLPARSE_INSIDE_X11_TARGET_ARG:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_ARG);
      handle_end_x11_target_arg (pstate, localname, prefix);
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_ENVIRONMENT);
      pstate->state = RUN2_XMLPARSE_INSIDE_GLOBAL_AFTER_ENVIRONMENT;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_TARGET:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_TARGET);
      pstate->state = RUN2_XMLPARSE_INSIDE_GLOBAL_AFTER_TARGET;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_ENVIRONMENT);
      pstate->state = RUN2_XMLPARSE_INSIDE_GDI_AFTER_ENVIRONMENT;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_GDI_TARGET:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_TARGET);
      pstate->state = RUN2_XMLPARSE_INSIDE_GDI_AFTER_TARGET;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_ENVIRONMENT);
      pstate->state = RUN2_XMLPARSE_INSIDE_X11_AFTER_ENVIRONMENT;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_X11_TARGET:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_TARGET);
      pstate->state = RUN2_XMLPARSE_INSIDE_X11_AFTER_TARGET;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT_PREPEND:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_PREPEND);
      pstate->state = RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT_APPEND:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_APPEND);
      pstate->state = RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT_SET:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_SET);
      pstate->state = RUN2_XMLPARSE_INSIDE_GLOBAL_ENVIRONMENT;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT_PREPEND:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_PREPEND);
      pstate->state = RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT_APPEND:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_APPEND);
      pstate->state = RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT_SET:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_SET);
      pstate->state = RUN2_XMLPARSE_INSIDE_GDI_ENVIRONMENT;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT_PREPEND:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_PREPEND);
      pstate->state = RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT_APPEND:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_APPEND);
      pstate->state = RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;
    case RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT_SET:
      (void) wrong_closure (pstate, localname, prefix, RUN2_ELN_SET);
      pstate->state = RUN2_XMLPARSE_INSIDE_X11_ENVIRONMENT;
      esp = run2_xml_stackpop (pstate);
      run2_destroy_element_and_state (esp);
      break;

    case RUN2_XMLPARSE_FINISH:
      warnMsg ("should not be closing any elements in this state");
      break;
    case RUN2_XMLPARSE_UNKNOWN_STATE:
      pstate->unknown_depth--;
      if (pstate->unknown_depth == 0)
          pstate->state = pstate->prev_state;
      break;
  }
}

xmlEntityPtr
run2_xml_getentity(void          *ctx,
                   const xmlChar *name)
{
  return xmlGetPredefinedEntity(name);
}

